package br.com.ipcalc;

import java.util.ArrayList;

public class Ipv4Calc {

	public String enderecoIp;
	public String mask;
	public String enderecoRede;
	public String enderecoBroadcast;
	public String hosts;
	public String enderecoIpBin;
	public String maskBin;
	public String range;
	public int maskBits;

	public Ipv4Calc(String enderecoIp, String mask) {
		super();
		this.enderecoIp = enderecoIp;
		this.mask = mask;
		maskBits();
		this.enderecoRede = arrayOctetosToFormatoIpString(enderecoRede(
				this.enderecoIp, this.mask));
		hosts();
		this.maskBin = arrayOctetosToFormatoIpString(enderecoIpStringToArrayOctetosBin(this.mask));
		this.enderecoIpBin = arrayOctetosToFormatoIpString(enderecoIpStringToArrayOctetosBin(this.enderecoIp));
		this.enderecoBroadcast = arrayOctetosToFormatoIpString(enderecoBroadcast(
				this.enderecoRede, this.mask));
		range();
	}

	public Ipv4Calc(String enderecoIp, int maskBits) {
		super();
		this.enderecoIp = enderecoIp;
		this.maskBits = maskBits;
		maskBitsToMask();
		this.enderecoRede = arrayOctetosToFormatoIpString(enderecoRede(
				this.enderecoIp, this.mask));
		hosts();
		this.maskBin = arrayOctetosToFormatoIpString(enderecoIpStringToArrayOctetosBin(this.mask));
		this.enderecoIpBin = arrayOctetosToFormatoIpString(enderecoIpStringToArrayOctetosBin(this.enderecoIp));
		this.enderecoBroadcast = arrayOctetosToFormatoIpString(enderecoBroadcast(
				this.enderecoRede, this.mask));
		range();
	}

	public String getEnderecoIp() {
		return enderecoIp;
	}

	public void setEnderecoIp(String enderecoIp) {
		this.enderecoIp = enderecoIp;
	}

	public String getMask() {
		return mask;
	}

	public void setMask(String mask) {
		this.mask = mask;
	}

	public String getEnderecoRede() {
		return enderecoRede;
	}

	public String getEnderecoBroadcast() {
		return enderecoBroadcast;
	}

	public String getHosts() {
		return hosts;
	}

	public String getEnderecoIpBin() {
		return enderecoIpBin;
	}

	public String getMaskBin() {
		return maskBin;
	}

	public int getMaskBits() {
		return maskBits;
	}

	public void setMaskBits(int maskBits) {
		this.maskBits = maskBits;
	}

	public ArrayList<String> getResultArray() {

		ArrayList<String> result = new ArrayList<String>();

		result.add("IP: " + this.enderecoIp);
		result.add("Mask: " + this.mask);
		result.add("Prefix: /" + this.maskBits);
		result.add("Network: " + this.enderecoRede);
		result.add("Broadcast: " + this.enderecoBroadcast);
		result.add("Hosts: " + this.hosts);
		result.add("Range: " + this.range);
		result.add("Mask Bin: " + this.enderecoIpBin);
		result.add("IP Bin: " + this.maskBin);

		return result;
	}

	public String getResult() {

		if (this.maskBits == 32) {
			this.enderecoRede = "-";
			this.enderecoBroadcast = "-";
		}

		String result = "IP: " + this.enderecoIp + "\n" + "Mask: " + this.mask
				+ "\n" + "Prefix: " + this.maskBits + "\n" + "Network: "
				+ this.enderecoRede + "\n" + "Broadcast: "
				+ this.enderecoBroadcast + "\n" + "Hosts: " + this.hosts;

		return result;
	}

	public String getResultBin() {
		if (this.maskBits == 32) {
			this.range = "-";
		}

		return "Range: " + this.range + "\n" + "IP Bin: " + "\n"
				+ this.enderecoIpBin + "\n" + "Mask Bin: " + "\n"
				+ this.maskBin + "\n";
	}

	public void range() {

		String[] redeBin = enderecoIpStringToArrayOctetosBin(this.enderecoRede);
		String[] broadcastBin = enderecoIpStringToArrayOctetosBin(this.enderecoBroadcast);
		String[] redeOctInt = arrayOctetosBinToArrayOctetosInt(redeBin);
		String[] broadcastOctInt = arrayOctetosBinToArrayOctetosInt(broadcastBin);

		redeOctInt[3] = String.valueOf((Integer.parseInt(redeOctInt[3]) + 1));
		broadcastOctInt[3] = String.valueOf((Integer
				.parseInt(broadcastOctInt[3]) - 1));

		this.range = arrayOctetosToFormatoIpString(redeOctInt) + " - "
				+ arrayOctetosToFormatoIpString(broadcastOctInt);

	}

	private void hosts() {

		String[] maskBin = enderecoIpStringToArrayOctetosBin(this.mask);
		double bits0 = getBits0(maskBin);

		int hostInt = Integer.parseInt(String.valueOf(Math.round(Math.pow(2.0,
				bits0)))) - 2;
		if (bits0 == 0) {
			hostInt = 0;
		}

		this.hosts = String.valueOf(hostInt);
	}

	private void maskBits() {
		String[] maskBin = enderecoIpStringToArrayOctetosBin(this.mask);

		this.maskBits = getBits1(maskBin);
	}

	private void maskBitsToMask() {

		String[] maskBin = new String[4];

		String maskString = "";

		for (int j = 0; j < this.maskBits; j++) {
			maskString = maskString + "1";
		}

		if (this.maskBits < 32) {
			int uns = 32 - this.maskBits;

			for (int j = 0; j < uns; j++) {
				maskString = maskString + "0";
			}
		}
		maskBin[0] = String.valueOf(binToInt(maskString.substring(0, 8)));
		maskBin[1] = String.valueOf(binToInt(maskString.substring(8, 16)));
		maskBin[2] = String.valueOf(binToInt(maskString.substring(16, 24)));
		maskBin[3] = String.valueOf(binToInt(maskString.substring(24, 32)));

		this.mask = arrayOctetosToFormatoIpString(maskBin);
	}

	/**
	 * Calcula o Endere�o de Broadcast a partir do IP e M�scara.
	 * 
	 * @param ip
	 *            Endere�o IP em String Ex.: "192.168.100.1"
	 * @param mask
	 *            M�scara em String Ex.: "255.255.255.0"
	 * 
	 * @return Retorna um endere�o de rede em Array de String de Octetos em
	 *         "inteiro" Ex.: {192,168,100,0}
	 * 
	 * @author Alexandre Antunes
	 */
	private String[] enderecoBroadcast(String enderecoRede, String mask) {

		String[] enderecoBroadcast = new String[4];
		String[] redeBin = enderecoIpStringToArrayOctetosBin(enderecoRede);
		String[] maskBin = enderecoIpStringToArrayOctetosBin(mask);
		String[] redeOctInt = arrayOctetosBinToArrayOctetosInt(redeBin);
		String[] maskOctInt = arrayOctetosBinToArrayOctetosInt(maskBin);

		for (int i = 0; i < maskOctInt.length; i++) {

			int octetoRede = Integer.parseInt(redeOctInt[i]);
			int octetoMask = ~Integer.parseInt(maskOctInt[i]);

			if (Integer.toBinaryString(octetoMask).length() == 32) {
				String octetoMaskString = Integer.toBinaryString(octetoMask)
						.substring(24);
				octetoMask = binToInt(octetoMaskString);
			}
			int octetoBroadcast = octetoRede | octetoMask;
			enderecoBroadcast[i] = String.valueOf(octetoBroadcast);
		}

		return enderecoBroadcast;

	}

	/**
	 * Calcula o Endere�o de Rede a partir do IP e M�scara.
	 * 
	 * @param ip
	 *            Endere�o IP em String Ex.: "192.168.100.1"
	 * @param mask
	 *            M�scara em String Ex.: "255.255.255.0"
	 * 
	 * @return Retorna um endere�o de rede em Array de String de Octetos em
	 *         "inteiro" Ex.: {192,168,100,0}
	 * 
	 * @author Alexandre Antunes
	 */
	private String[] enderecoRede(String ip, String mask) {

		String[] enderecoRede = new String[4];
		String[] ipBin = enderecoIpStringToArrayOctetosBin(ip);
		String[] maskBin = enderecoIpStringToArrayOctetosBin(mask);
		String[] ipOctInt = arrayOctetosBinToArrayOctetosInt(ipBin);
		String[] maskOctInt = arrayOctetosBinToArrayOctetosInt(maskBin);

		for (int i = 0; i < maskOctInt.length; i++) {

			int octetoIp = Integer.parseInt(ipOctInt[i]);
			int octetoMask = Integer.parseInt(maskOctInt[i]);
			int octetoRede = octetoIp & octetoMask;
			enderecoRede[i] = String.valueOf(octetoRede);
		}

		return enderecoRede;

	}

	/**
	 * Recebe um Array Octetos e retorna no formato de um endere�o IP em String.
	 * 
	 * @param octetosBinString
	 *            Array de String de Octetos em "bin�rio" Ex.: {192,168,100,1}
	 *            ou {11000000,10101000,01100100,00000001}
	 * 
	 * @return Retorna um Endere�o IP em String Ex.: "192.168.100.1" ou
	 *         "11000000.10101000.01100100.00000001"
	 * 
	 * @author Alexandre Antunes
	 */
	private String arrayOctetosToFormatoIpString(String[] octetosString) {

		String enderecoIpString = octetosString[0] + "." + octetosString[1]
				+ "." + octetosString[2] + "." + octetosString[3];

		return enderecoIpString;
	}

	/**
	 * Recebe um Array de String de Octetos em "bin�rio" e retorna um endere�o
	 * IP em String.
	 * 
	 * @param octetosBinString
	 *            Array de String de Octetos em "bin�rio" Ex.:
	 *            {11000000,10101000,01100100,00000001}
	 * 
	 * @return Retorna um Array de String de Octetos em "Inteiro" Ex.:
	 *         "{192,168,100,1}"
	 * @author Alexandre Antunes
	 */
	private String[] arrayOctetosBinToArrayOctetosInt(String[] octetosBinString) {

		String octetosInt[] = new String[4];

		for (int i = 0; i < octetosBinString.length; i++) {

			octetosInt[i] = String.valueOf(Math
					.round(binToInt(octetosBinString[i])));
		}
		return octetosInt;
	}

	/**
	 * Recebe um endere�o IP em String e retorna um Array de String de Octetos
	 * em "bin�rio".
	 * 
	 * @param endereco
	 *            Endere�o IP em String Ex.: "192.168.100.1"
	 * 
	 * @return Retorna um Array de String de Octetos em "bin�rio" Ex.:
	 *         {11000000,10101000,01100100,00000001}
	 * 
	 * @author Alexandre Antunes
	 */
	private String[] enderecoIpStringToArrayOctetosBin(String endereco) {

		String[] octetosBin = endereco.split("[.]");

		for (int i = 0; i < octetosBin.length; i++) {

			String octeto = Integer.toBinaryString(Integer
					.parseInt(octetosBin[i]));
			if (octeto.length() < 8) {

				int zeros = 8 - octeto.length();

				for (int j = 0; j < zeros; j++) {
					octeto = "0" + octeto;
				}
			}
			octetosBin[i] = octeto;
		}
		return octetosBin;
	}

	/**
	 * Recebe um String de bits e retorna o inteiro .
	 * 
	 * @param bin
	 *            String de bits Ex.: "11000000"
	 * 
	 * @return Retorna o inteiro correspondente Ex.: 192
	 * 
	 * @author Alexandre Antunes
	 */
	private int binToInt(String bin) {

		double octetoTotalDouble = 0;
		char[] octeto = bin.toCharArray();

		double potencia = 7.0;
		for (int j = 0; j < octeto.length; j++) {

			String bit = String.valueOf(octeto[j]);

			if (bit.contains("1")) {
				double octetoDouble = Math.pow(2.0, potencia);
				octetoTotalDouble = octetoTotalDouble + octetoDouble;
			}
			potencia--;
		}
		int octetoInt = Integer.parseInt(String.valueOf(Math
				.round(octetoTotalDouble)));

		return octetoInt;
	}

	private int getBits0(String[] mask) {

		String bits = mask[0] + mask[1] + mask[2] + mask[3];
		char[] arrayBits = bits.toCharArray();
		int bits0 = 0;

		for (int i = 0; i < arrayBits.length; i++) {

			String bit = String.valueOf(arrayBits[i]);
			if (bit.contains("0")) {
				bits0++;
			}
		}
		return bits0;
	}

	private int getBits1(String[] mask) {

		String bits = mask[0] + mask[1] + mask[2] + mask[3];
		char[] arrayBits = bits.toCharArray();
		int bits1 = 0;

		for (int i = 0; i < arrayBits.length; i++) {

			String bit = String.valueOf(arrayBits[i]);
			if (bit.contains("1")) {
				bits1++;
			}
		}
		return bits1;
	}
}
